從前面的經驗知道,編寫、編譯以及管理 Shader 不是一件容易的事,如果我們將他封裝起來,讓這個管理器能夠從硬碟讀取 Shader,接著編譯再 Link 起來,這樣使用起來會方便許多。
#pragma once
#include "pch.h"
#include <glad/glad.h>
#include "glm/glm.hpp"
#include "glm/glm.hpp"
class Shader {
public:
Shader() = default;
Shader(const char *vertPath, const char *fragPath);
~Shader();
void bind();
void unbind();
uint32_t getShaderID() const { return program; }
int getLocationByName(const std::string &name) const;
void setInt(const std::string &name, int value);
void setIntArray(const std::string &name, int *values, uint32_t count);
//
void setFloat(const std::string &name, float value);
void setFloat2(const std::string &name, const glm::vec2 &value);
void setFloat3(const std::string &name, const glm::vec3 &value);
void setFloat4(const std::string &name, const glm::vec4 &value);
//
void setMat2(const std::string &name, const glm::mat2 &matrix);
void setMat3(const std::string &name, const glm::mat3 &matrix);
void setMat4(const std::string &name, const glm::mat4 &matrix);
static uint32_t CompileShader(GLenum type, const char **src);
static uint32_t LinkShaderProgram(uint32_t vertex, uint32_t fragment);
private:
std::string vertSource, fragSource;
uint32_t program = 0;
};
program
儲存的其實就是此 Shader 的 ID,而我可們 Construct 的時候可以傳入 Vertex Shader 跟 fragment Shader 的檔案路徑,這樣我們就可以不用把 Shader 的 code 寫在程式裡面了。
我們還加入了一些功能。bind
就是用來激活 Shader的,set ...
就是用來設定uniform
參數的。
用 C++ 讀檔將 Shader 內容儲存到 std::string
讀檔
bool LoadFileContent(std::string &s, const char *path) {
std::ifstream ifs(path);
if (ifs) {
s.assign((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
return true;
}
return false;
}
接著要編譯並連結
Shader::Shader(const char *vertPath, const char *fragPath) {
assert(LoadFileContent(vertSource, vertPath) == true);
assert(LoadFileContent(fragSource, fragPath) == true);
const char *vertSrc = vertSource.c_str();
uint32_t vert = CompileShader(GL_VERTEX_SHADER, &vertSrc);
const char *fragSrc = fragSource.c_str();
uint32_t frag = CompileShader(GL_FRAGMENT_SHADER, &fragSrc);
assert(vert && frag);
program = LinkShaderProgram(vert, frag);
assert(program);
glDeleteShader(vert);
glDeleteShader(frag);
}
uint32_t Shader::CompileShader(GLenum type, const char **src) {
uint32_t shader;
shader = glCreateShader(type);
glShaderSource(shader, 1, src, nullptr);
glCompileShader(shader);
int success;
char log[512];
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader, 512, nullptr, log);
printf("Error Shader %s compile error\n%s\n", type == GL_VERTEX_SHADER ? "Vertex" : "Fragment", log);
return 0;
}
return shader;
}
uint32_t Shader::LinkShaderProgram(uint32_t vertex, uint32_t fragment) {
uint32_t program = glCreateProgram();
glAttachShader(program, vertex);
glAttachShader(program, fragment);
glLinkProgram(program);
//
int success;
char log[512];
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(program, 512, nullptr, log);
printf("Error Shader Linking error\n%s\n", log);
return 0;
}
return program;
}
bind/unbind 非常之簡單。
void bind() {
glUseProgram(program);
}
void unbind() {
glUseProgram(0);
}
setter也是很簡單
void Shader::setInt(const std::string &name, int value) {
glUniform1i(getLocationByName(name), value);
}
void Shader::setIntArray(const std::string &name, int *values, uint32_t count) {
glUniform1iv(getLocationByName(name), count, values);
}
void Shader::setFloat(const std::string &name, float value) {
glUniform1f(getLocationByName(name), value);
}
void Shader::setFloat2(const std::string &name, const glm::vec2 &value) {
glUniform2f(getLocationByName(name), value.x, value.y);
}
這樣就成功封裝我們的 Shader 了
使用方法會像這樣
Shader shader("vertexShaderPath", "fragmentShaderPath");
...
while(running) {
shader.bind();
shader.setXX("uniform", value);
render();
shader.unbind();
}